{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TensorFlow Script Mode with Pipe Mode Input\n",
    "\n",
    "\n",
    "SageMaker Pipe Mode is an input mechanism for SageMaker training containers based on Linux named pipes. SageMaker makes the data available to the training container using named pipes, which allows data to be downloaded from S3 to the container while training is running. For larger datasets, this dramatically improves the time to start training, as the data does not need to be first downloaded to the container. To learn more about pipe mode, please consult the AWS documentation at: https://docs.aws.amazon.com/sagemaker/latest/dg/your-algorithms-training-algo.html#your-algorithms-training-algo-running-container-trainingdata.\n",
    "\n",
    "In this tutorial, we show you how to train a TensorFlow estimator using data read with SageMaker Pipe Mode. We use the SageMaker PipeModeDataset class - a special TensorFlow Dataset built specifically to read from SageMaker Pipe Mode data. This Dataset is available in our TensorFlow containers for TensorFlow versions 1.7.0 and up. It's also open-sourced at https://github.com/aws/sagemaker-tensorflow-extensions and can be built into custom TensorFlow images for use in SageMaker.\n",
    "\n",
    "Although you can also build the PipeModeDataset into your own containers, in this tutorial we'll show how you can use the PipeModeDataset by launching training from the SageMaker Python SDK. The SageMaker Python SDK helps you deploy your models for training and hosting in optimized, production-ready containers in SageMaker. The SageMaker Python SDK is easy to use, modular, extensible and compatible with TensorFlow and many other deep learning frameworks.\n",
    "\n",
    "Different collections of S3 files can be made available to the training container while it's running. These are referred to as \"channels\" in SageMaker. In this example, we use two channels - one for training data and one for evaluation data. Each channel is mapped to S3 files from different directories. The SageMaker PipeModeDataset knows how to read from the named pipes for each channel given just the channel name. When we launch SageMaker training we tell SageMaker what channels we have and where in S3 to read the data for each channel.\n",
    "\n",
    "\n",
    "## Setup\n",
    "The following code snippet sets up some variables we'll need later on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sagemaker import get_execution_role\n",
    "from sagemaker.session import Session\n",
    "\n",
    "# S3 bucket for saving code and model artifacts.\n",
    "# Feel free to specify a different bucket here if you wish.\n",
    "bucket = Session().default_bucket()\n",
    "\n",
    "# Location to save your custom code in tar.gz format.\n",
    "custom_code_upload_location = 's3://{}/tensorflow_scriptmode_pipemode/customcode'.format(bucket)\n",
    "\n",
    "# Location where results of model training are saved.\n",
    "model_artifacts_location = 's3://{}/tensorflow_scriptmode_pipemode/artifacts'.format(bucket)\n",
    "\n",
    "# IAM execution role that gives SageMaker access to resources in your AWS account.\n",
    "role = get_execution_role()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Complete training source code\n",
    "\n",
    "In this tutorial we train a TensorFlow LinearClassifier using pipe mode data. The TensorFlow training script is contained in following file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pygmentize \"pipemode.py\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above script is compatible with the SageMaker TensorFlow script mode container. (See: [Preparing TensorFlow Training Script](https://github.com/aws/sagemaker-python-sdk/tree/master/src/sagemaker/tensorflow#preparing-a-script-mode-training-script)).\n",
    "\n",
    "Using a `PipeModeDataset` to train an estimator using a Pipe Mode channel, we can construct an function that reads from the channel and return an `PipeModeDataset`. This is a TensorFlow Dataset specifically created to read from a SageMaker Pipe Mode channel. A `PipeModeDataset` is a fully-featured TensorFlow Dataset and can be used in exactly the same ways as a regular TensorFlow Dataset can be used.\n",
    "\n",
    "The training and evaluation data used in this tutorial is synthetic. It contains a series of records stored in a TensorFlow Example protobuf object. Each record contains a numeric class label and an array of 1024 floating point numbers. Each array is sampled from a multi-dimensional Gaussian distribution with a class-specific mean. This means it is possible to learn a model using a TensorFlow Linear classifier which can classify examples well. Each record is separated using RecordIO encoding (though the `PipeModeDataset` class also supports the TFRecord format as well).\n",
    "\n",
    "The training and evaluation data were produced using the benchmarking source code in the sagemaker-tensorflow-extensions benchmarking sub-package. If you want to investigate this further, please visit the GitHub repository for sagemaker-tensorflow-extensions at https://github.com/aws/sagemaker-tensorflow-extensions.\n",
    "\n",
    "The following example code shows how to construct a `PipeModeDataset`.\n",
    "\n",
    "```python\n",
    "from sagemaker_tensorflow import `PipeModeDataset`\n",
    "\n",
    "\n",
    "# Simple example data - a labeled vector.\n",
    "features = {\n",
    "    'data': tf.FixedLenFeature([], tf.string),\n",
    "    'labels': tf.FixedLenFeature([], tf.int64),\n",
    "}\n",
    "\n",
    "# A function to parse record bytes to a labeled vector record\n",
    "def parse(record):\n",
    "    parsed = tf.parse_single_example(record, features)\n",
    "    return ({\n",
    "        'data': tf.decode_raw(parsed['data'], tf.float64)\n",
    "    }, parsed['labels'])\n",
    "\n",
    "# Construct a `PipeModeDataset` reading from a 'training' channel, using\n",
    "# the TF Record encoding.\n",
    "ds = `PipeModeDataset`(channel='training', record_format='TFRecord')\n",
    "\n",
    "# The `PipeModeDataset` is a TensorFlow Dataset and provides standard Dataset methods\n",
    "ds = ds.repeat(20)\n",
    "ds = ds.prefetch(10)\n",
    "ds = ds.map(parse, num_parallel_calls=10)\n",
    "ds = ds.batch(64)\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Running training using the Python SDK\n",
    "\n",
    "We can use the SDK to run our local training script on SageMaker infrastructure.\n",
    "\n",
    "1. Pass the path to the pipemode.py file, which contains the functions for defining your estimator, to the ``sagemaker.tensorflow.TensorFlow`` init method.\n",
    "2. Pass the S3 location that we uploaded our data to previously to the ``fit()`` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sagemaker.tensorflow import TensorFlow\n",
    "\n",
    "tensorflow = TensorFlow(entry_point='pipemode.py',\n",
    "                        role=role,\n",
    "                        framework_version='1.13',\n",
    "                        input_mode='Pipe',\n",
    "                        output_path=model_artifacts_location,\n",
    "                        code_location=custom_code_upload_location,\n",
    "                        train_instance_count=1,\n",
    "                        py_version='py3',\n",
    "                        train_instance_type='ml.c4.xlarge')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After we've created the SageMaker Python SDK TensorFlow object, we can call ``fit()`` to launch TensorFlow training:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "import boto3\n",
    "\n",
    "# use the region-specific sample data bucket\n",
    "region = boto3.Session().region_name\n",
    "\n",
    "train_data = 's3://sagemaker-sample-data-{}/tensorflow/pipe-mode/train'.format(region)\n",
    "eval_data = 's3://sagemaker-sample-data-{}/tensorflow/pipe-mode/eval'.format(region)\n",
    "\n",
    "tensorflow.fit({'train':train_data, 'eval':eval_data})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After training finishes, the trained model artifacts will be uploaded to S3. This following example notebook shows how to deploy a model trained with script mode: https://github.com/awslabs/amazon-sagemaker-examples/tree/master/sagemaker-python-sdk/tensorflow_script_mode_training_and_serving"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "conda_tensorflow_p36",
   "language": "python",
   "name": "conda_tensorflow_p36"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
